/**
 * 
 */
package org.demetra.core;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangPid;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpMbox;

/**
 * This is a request-reply loop behaviour class.
 * 
 * @author vjache
 */
public abstract class WorkerLoop implements Runnable {

	public static final OtpErlangAtom JETS_REQ_TAG_ATOM = new OtpErlangAtom("jets_req");
	public static final OtpErlangAtom JETS_REP_TAG_ATOM = new OtpErlangAtom("jets_rep");
	public static final OtpErlangTuple JETS_CLOSE_MSG = new OtpErlangTuple(new OtpErlangObject[]{new OtpErlangAtom("jets_req"),new OtpErlangAtom("close")});

	private OtpMbox _mbox;
	private String _label;

	/**
	 * 
	 */
	public WorkerLoop(OtpMbox aMbox) {
		_mbox = aMbox;
		_label=String.format("%s [pid=%s,name=%s]",getClass().getSimpleName(), mbox().self(),mbox().getName());
	}
	public WorkerLoop(OtpMbox aMbox,String aLabel) {
		_mbox = aMbox;
		_label = aLabel;
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		try {
			init();
			while(true)
			{

				OtpErlangObject otpObj = mbox().receive();
				if(otpObj.equals(JETS_CLOSE_MSG))
					break;

				try
				{
					//{jets_req,ReplyPid,OpCode,ReqData}
					OtpErlangTuple otpMsg = (OtpErlangTuple) otpObj;
					if(otpMsg.arity()!=4)
					{
						logError("Wrong arity of a tuple '%s'.",otpMsg);
						continue;
					}
					OtpErlangAtom tag = (OtpErlangAtom)(otpMsg.elementAt(0));
					if(!tag.equals(JETS_REQ_TAG_ATOM))
					{
						logError("Wrong tag in a tuple '%s'.",otpMsg);
						continue;
					}
					final OtpErlangPid replyTo = (OtpErlangPid)(otpMsg.elementAt(1));
					final OtpErlangAtom opCode = (OtpErlangAtom)(otpMsg.elementAt(2));
					final OtpErlangObject reqData = otpMsg.elementAt(3);

					//Handle request
					OtpErlangObject repData;
					try
					{
						repData = handleRequest(opCode,reqData);
					}
					catch (JetsException e) {
						logError("Problem with request '%s': %s.",otpMsg,e);
						repData = toOtpObject(e);
					}
					catch (Throwable t) {
						logError("Unhandled problem encountered: %s.", t);
						repData = toOtpObject(t);
					}
					//{jets_rep,ReplyPid,OpCode,RepData}
					mbox().send(replyTo, 
							tuple(
									JETS_REP_TAG_ATOM,
									mbox().self(),
									opCode,
									repData));
				}
				catch(ClassCastException e)
				{
					logError(e,"Bad in-message '%s'.",otpObj);
					continue;
				}

			}
			mbox().close();
		} 
		catch (OtpErlangExit e) {
			mbox().exit(toOtpObject(e));
		} 
		catch (OtpErlangDecodeException e) {
			logError("Decode message error: %s \n Exiting loop.", e);
			mbox().exit(toOtpObject(e));
		}
		catch (Throwable t)
		{
			logError("Fatal error: %s \n Exiting loop.", t);
			mbox().exit(toOtpObject(t));
		}
		finally
		{
			try {
				close();
			} catch (Exception e) {
				logError(e);
			}
		}
	}

	/**
	 * This method inwoked just before request-reply loop.
	 * @throws Exception 
	 */
	protected void init() throws Exception
	{
		Thread.currentThread().setName(_label);
		logInfo("%s started.",_label);
	}

	/**
	 * Returns data to reply.
	 * @param aOpCode - code of an operation.
	 * @param aRequestData - a data to apply with this request.
	 * @return OtpErlangObject
	 */
	protected abstract OtpErlangObject handleRequest(
			OtpErlangAtom aOpCode,
			OtpErlangObject aRequestData);

	/**
	 * Release resources here.
	 * @throws Exception 
	 */
	protected void close() throws Exception
	{
		Thread.currentThread().setName("free");
		logInfo("%s stopped.",_label);
	}

	protected OtpErlangObject toOtpObject(Throwable t) {
		return JetsException.toOtpObject(t,mbox().self());
	}

	protected OtpErlangObject toOtpObject(JetsException e) {
		return e.toOtpObject(mbox().self());
	}

	protected OtpErlangObject toOtpObject(OtpErlangDecodeException e) {
		return new OtpErlangTuple(new OtpErlangObject[]{
				new OtpErlangAtom("decode_msg_err"),
				mbox().self(),
				new OtpErlangString(e.getMessage())
		});
	}

	protected OtpErlangObject toOtpObject(OtpErlangExit e) {
		return new OtpErlangTuple(new OtpErlangObject[]{
				new OtpErlangAtom("exit"),
				mbox().self(),
				e.reason()
		});
	}

	protected final OtpMbox mbox()
	{
		return _mbox;
	}

	protected final OtpErlangTuple tuple(OtpErlangObject... aObjs)
	{
		return new OtpErlangTuple(aObjs);
	}

	protected final OtpErlangAtom atom(String aStr)
	{
		return new OtpErlangAtom(aStr);
	}

	protected final void logInfo(String aFormat,Object... aArgs)
	{
		System.out.println(String.format(aFormat, aArgs));
	}
	protected final void logError(Throwable t,String aFormat,Object... aArgs)
	{
		logError(aFormat, aArgs);
		logError(t);
	}
	protected final void logError(String aFormat,Object... aArgs)
	{
		System.err.println(String.format(aFormat, aArgs));
	}
	protected final void logError(Throwable t)
	{
		t.printStackTrace(System.err);
	}
}
